package com.punch.oauth.controller;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.punch.common.constant.SecurityConstants;
import com.punch.common.dto.login.AppLoginUserDTO;
import com.punch.common.exception.BusinessException;
import com.punch.common.feign.FeignPlatformUserService;
import com.punch.common.simple.redis.utils.RedisUtil;
import com.punch.common.utils.DateUtil;
import com.punch.common.utils.ResponseUtil;
import com.punch.oauth.constant.ClientConstant;
import com.punch.oauth.exception.UaaExceptionEnum;
import com.punch.oauth.mobile.MobileCodeAuthenticationToken;
import com.punch.oauth.service.impl.RedisClientDetailsService;
import com.punch.oauth.vo.LoginUserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;

/**
 * 平台用户登录
 */
@RestController
@Slf4j
@Api(value = "PlatformLoginController", tags = "app平台登陆")
public class PlatformLoginController {


    @Autowired
    private RedisClientDetailsService clientDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Resource
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private FeignPlatformUserService feignPlatformUserService;

    @Autowired
    private RedisUtil redisUtil;


    /**
     * app端用户登录获取用户名与密码
     *
     * @param appLoginUserDTO 登录表单
     * @param request         请求
     * @param response        响应
     * @throws IOException
     */
    @ApiOperation(value = "app端用户登录获取用户名与密码")
    @PostMapping(value = "/oauth/app/user/login", produces = "application/json;charset=UTF-8")
    public void getUserTokenInfo(@RequestBody AppLoginUserDTO appLoginUserDTO, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String clientType = request.getHeader("clientType");
        if (!ClientConstant.APP.equals(clientType)) {
            throw new UnapprovedClientAuthenticationException("非app端不能登录");
        }
        if (StringUtils.isEmpty(appLoginUserDTO.getPassword())) {
            throw new UnapprovedClientAuthenticationException("密码为空");
        }
        //limit(appLoginUserDTO.getMobile());
        appLoginUserDTO.setCode("");
        feignPlatformUserService.saveOrUpdatePlatformUser(appLoginUserDTO);
        String user = appLoginUserDTO.getMobile() + "-" + clientType;
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, appLoginUserDTO.getPassword());
        LoginUserVO loginUserVO = new LoginUserVO();
        writerToken(clientType, appLoginUserDTO.getMobile(), response, token, loginUserVO);
    }

    /**
     * 根据手机验证获取token登录
     *
     * @param appLoginUserDTO 登录表单
     * @param response        响应
     * @throws IOException
     */
    @ApiOperation(value = "app端手机号和验证码获取token")
    @PostMapping(value = "/oauth/app/user/code", produces = "application/json;charset=UTF-8")
    public void getUserTokenByCode(@RequestBody AppLoginUserDTO appLoginUserDTO, HttpServletRequest request, HttpServletResponse response) throws IOException {
        if (StringUtils.isEmpty(appLoginUserDTO.getCode())) {
            throw new UnapprovedClientAuthenticationException("验证码为空");
        }
        String clientType = request.getHeader("clientType");
        if (!ClientConstant.APP.equals(clientType)) {
            throw new UnapprovedClientAuthenticationException("非app端不能登录");
        }
        appLoginUserDTO.setPassword("");
        feignPlatformUserService.saveOrUpdatePlatformUser(appLoginUserDTO);
        String mobile = appLoginUserDTO.getMobile();
        String user = mobile + "-" + clientType;
        MobileCodeAuthenticationToken token = new MobileCodeAuthenticationToken(user);
        //limit(mobile);
        //验证验证码
        if (!verifyAuthCode(appLoginUserDTO.getCode(), mobile)) {
            throw new BusinessException(1, "验证码错误");
        }
        LoginUserVO loginUserVO = new LoginUserVO();
        writerToken(clientType, mobile, response, token, loginUserVO);
    }


    /**
     * app端首页重置用户密码
     *
     * @param appLoginUserDTO 用户信息
     * @param request         请求信息
     */
    @ApiOperation(value = "app端首页重置用户密码")
    @PostMapping(value = "/oauth/app/user/pwd-forget", produces = "application/json;charset=UTF-8")
    public void pwdForget(@RequestBody AppLoginUserDTO appLoginUserDTO, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String clientType = request.getHeader("clientType");
        if (!ClientConstant.APP.equals(clientType)) {
            throw new UnapprovedClientAuthenticationException("非app端不能登录");
        }
        if (StringUtils.isEmpty(appLoginUserDTO.getCode())) {
            throw new UnapprovedClientAuthenticationException("验证码为空");
        }
        if (StringUtils.isEmpty(appLoginUserDTO.getPassword())) {
            throw new UnapprovedClientAuthenticationException("密码为空");
        }
        if (!verifyAuthCode(appLoginUserDTO.getCode(), appLoginUserDTO.getMobile())) {
            throw new BusinessException(1, "验证码错误");
        }
        feignPlatformUserService.forgetUserPwd(appLoginUserDTO);
        String user = appLoginUserDTO.getMobile() + "-" + clientType;
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, appLoginUserDTO.getPassword());
        LoginUserVO loginUserVO = new LoginUserVO();
        writerToken(clientType, appLoginUserDTO.getMobile(), response, token, loginUserVO);
    }

    /**
     * 写入token到请求头中
     *
     * @param clientId 客户端id
     * @param response 响应
     * @param token    token
     * @throws IOException
     */
    private void writerToken(String clientId, String mobile, HttpServletResponse response, AbstractAuthenticationToken token, LoginUserVO loginUserVO) throws IOException {
        try {
            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "password");
            Authentication authentication = authenticationManager.authenticate(token);
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
            OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
            oAuth2Authentication.setAuthenticated(true);
            loginUserVO.setAuthorizationToken("Bearer " + oAuth2AccessToken.getValue());
            ResponseUtil.responseSucceed(objectMapper, response, loginUserVO);
        } catch (BadCredentialsException e) {
            //错误5次账号锁定
            limit(mobile);
            exceptionHandler(response, "用户名或密码错误");
        } catch (InternalAuthenticationServiceException e) {
            exceptionHandler(response, e.getMessage());
        } catch (Exception e) {
            exceptionHandler(response, e);
        }
    }

    private void exceptionHandler(HttpServletResponse response, Exception e) throws IOException {
        log.error("exceptionHandler-error:", e);
        exceptionHandler(response, e.getMessage());
    }

    private void exceptionHandler(HttpServletResponse response, String msg) throws IOException {
        ResponseUtil.responseFailed(objectMapper, response, msg);
    }

    //对输入的验证码进行校验
    private boolean verifyAuthCode(String authCode, String mobile) {
        if (StrUtil.isBlank(authCode)) {
            return false;
        }
        String realAuthCode = (String) redisUtil.getValue(SecurityConstants.DEFAULT_CODE_KEY + ":" + mobile);
        return authCode.equals(realAuthCode);
    }


    private void limit(String mobile) {
        String mobileLimitKey = SecurityConstants.MOBILE_LIMIT_PRE_KEY + mobile;
        String mobileLimit = (String) redisUtil.getValue(mobileLimitKey);
        if (StrUtil.isBlank(mobileLimit)) {
            redisUtil.setValue(mobileLimitKey, "1", DateUtil.getRemainSecondsOneDay(LocalDateTime.now()));
        } else {
            //同一个ip每天最多5次
            Integer limit = Integer.valueOf(mobileLimit);
            if (limit > 5) {
                log.error("该ip每天五次超发:{}--手机号{}", mobile, mobile);
                throw new BusinessException(UaaExceptionEnum.EXCEED_PASSWORD_ERROR_TIME);
            }
            limit++;
            redisUtil.setValue(mobileLimitKey, String.valueOf(limit), DateUtil.getRemainSecondsOneDay(LocalDateTime.now()));
        }
    }
}
